package compi;

import java.io.IOException;
import java.util.Map.Entry;
import java.util.Vector;

import data.Atrib;
import data.TablaSimbolos;
import data.Token;
import data.TipoToken;

public class Asintac
{
	private LectorTokens _lectorTokens;
	private CodInt _codint;
	private Token _token;
	private int _id;
	private int _erroresSintacticos;
	private int _erroresSemanticos;
	private String _metodo;
	
	/**
	 * Constructor
	 * @param nomFich
	 * @throws IOException
	 */
	public Asintac(String nomFich) throws IOException
	{
		_lectorTokens = new LectorTokens(nomFich);
		_codint = new CodInt();
		_erroresSintacticos = 0;
		_erroresSemanticos = 0;
		_id = 1;
		_metodo = "";
	}
	
	/**
**** * **************************************
**** * Funciones Auxiliares
**** * **************************************
	 */

	/**
	 * Devolver numero de errores
	 */
	public int numErrores()
	{
		return numErroresLexicos() + numErroresSemanticos() + numErroresSintacticos();
	}
	
	public int numErroresLexicos()
	{
		return _lectorTokens.getErrorCount();
	}
	
	public int numErroresSemanticos()
	{
		return _erroresSemanticos;
	}
	
	public int numErroresSintacticos()
	{
		return _erroresSintacticos;
	}
	
	/**
	 * Empareja lo que se ha leido con lo que
	 * se deberia haber leido
	 */
	private void emparejar(String token) 
	{

		if (! _token.getContent().equals(token) )
		{
			_erroresSintacticos++;
			System.err.print("("+_lectorTokens.numLinea()+"): ");
            System.err.println("Se esperaba "+token+" y se tiene "+_token.getContent());
		}
	}

	/**
	 * Genera declaraciones para todos los elementos
	 * de la lista y del tipo especificado
	 */
	private void generarListaId(Vector<Object> lista, String tipo)
	{
		generarListaId(lista, tipo, "");
	}
	
	private void generarListaId(Vector<Object> lista, String tipo, String tipoRef)
	{
		String elemento;
		
		while (!(lista.isEmpty()))
		{
			elemento = lista.firstElement().toString();
			// Devuelve null si se ha insertado correctamente
			// el tipo de la variable ya insertada en caso contrario
			String tipoAnterior = TablaSimbolos.getInstancia().insertarVariable(_metodo + elemento, tipo);
			
			// Errores semanticos
			if ( tipoAnterior != null && tipoAnterior.equals(tipo) )
			{
				System.err.println("Warning (" + _lectorTokens.numLinea() + "): "
						+ "La variable "+ elemento 
						+ " ya estaba declarada. Obviando segunda declaracion...");
			}
			else if ( tipoAnterior != null )
			{
				_erroresSemanticos++;
				System.err.println("(" + _lectorTokens.numLinea() + "): La variable "
						+ elemento + " ya estaba declarada como " + tipoAnterior + ".");
			
			}
			else
				_codint.addInst(tipoRef+tipo + " " + elemento + ";");
			
			lista.removeElementAt(0);
		}

	}
	
	/**
	 * Generar metodo
	 */
	private void generarMetodo(String nom, Vector<Object> tipoParams, Vector<Object> valref)
	{	
		Entry<Vector<Object>, Vector<Object>> tipos = 
					TablaSimbolos.getInstancia().insertarMetodo(nom, tipoParams, valref);
		if ( tipos != null )
		{
			_erroresSemanticos++;
			System.err.println("(" + _lectorTokens.numLinea() + "): El metodo "
					+ nom + " ya estaba declarado como "+ tipos.getKey() +".");
		}
		else
		{
			_metodo = nom + ".";
		}
	}

	/**
	 * Concatena dos vectores
	 */
	private Vector<Object> concatenar(Vector<Object> v1, Vector<Object> v2) 
	{
		v1.addAll(v2);
		v2.clear();
		
		return v1;
	}

	/**
	 * Obtiene un nuevo identificador
	 */
	private String nuevoId() 
	{
		return "t" + _id++;
	}
	
	/**
	 * Siguiente token
	 */
	private void nextToken()
	{
		_token = _lectorTokens.nextToken();
	}
	
	/**
	 * Existe id de variable
	 */
	private String getTipoVariable(String nom)
	{
		String aux1 = TablaSimbolos.getInstancia().getTipoVariable(_metodo + nom);
		if ( aux1 != null ) return aux1;
		else return TablaSimbolos.getInstancia().getTipoVariable(nom);
	}
	
	/**
	 * Existe id del metodo
	 */
	private Vector<Object> getTiposMetodo(String nom)
	{
		return TablaSimbolos.getInstancia().getTiposVarsMetodo(nom);
	}
	
	/**
	 * Error: la variable no existe
	 */
	private void errorVarNoExistente(String nom)
	{
		System.err.println("("+_lectorTokens.numLinea()+"): "
				+ "La variable "+nom+" no ha sido declarada.");
		_erroresSemanticos++;
	}
	
	/**
	 * Error: el metodo no existe
	 */
	private void errorMetNoExistente(String nom)
	{
		System.err.println("("+_lectorTokens.numLinea()+"): "
				+ "El metodo "+nom+" no ha sido declarado.");
		_erroresSemanticos++;
	}
	
	/**
	 * Devuelve una lista de elementos del tipo
	 */
	private Vector<Object> crearListaTipoParams(int num, String tipo)
	{
		Vector<Object> ret = new Vector<Object>();
		
		for ( int i = 0; i < num; i++ )
			ret.add(tipo);
		
		return ret;
	}
	
	
	/**
	 * Devuelve una lista de elementos de valref
	 */
	private Vector<Object> crearListaValrefParams(int num, String tipo)
	{
		Vector<Object> ret = new Vector<Object>();
		tipo = tipo.replace("_", "");
		
		for ( int i = 0; i < num; i++ )
			ret.add(tipo);
		
		return ret;
	}
	
	/**
	 * Muestra error si los tipos son distintos
	 * @param tipo
	 * @param tipo2
	 */
	private void errorTiposDistintos(String tipo, String tipo2) 
	{
		System.err.println("("+_lectorTokens.numLinea()+"): "
				+ "Los tipos de la asignacion no concuerdan. Se esperaba " + tipo + " y se ha obtenido " + tipo2 + ".");
		_erroresSemanticos++;
	}

	/**
	 * Comprobar que los parametros concuerdan
	 */
	private boolean comprobarParametros(Vector<Object> tipos,
			Vector<Object> listaid) 
	{	
		if(tipos.size() != listaid.size())
		{
			mostrarErrorParametros(tipos,listaid);
			return false;
		}
		
		for( int i = 0; i < tipos.size() ; i++ )	{
			if(!tipos.get(i).equals(listaid.get(i))){
				mostrarErrorParametros(tipos, listaid);
				return false;
			}
		}
			
		return true;
	}
	
	/**
	 * escribirLlamada
	 */
	private void escribirLlamada(String nom, Vector<Object> params)
	{
		// ref o val de las variables
		Vector<Object> tipos = TablaSimbolos.getInstancia().getValRefVarsMetodo(nom);
		
		for ( int i = 0; i < params.size(); i++ )
			_codint.addInst("param_"+tipos.get(i)+" "+params.get(i)+";");
		
		_codint.addInst("call "+nom+";");
	}
	
	/**
	 * Muestra error cuando los parametros son incorrectos
	 */
	private void mostrarErrorParametros(Vector<Object> tipos,
			Vector<Object> listaid){
		System.err.println("("+_lectorTokens.numLinea()+"): "
				+ "Los parametros del metodo no concuerdan. Se esperaban " + tipos + " y se ha obtenido " + listaid + ".");
		_erroresSemanticos++;
	}
	
	/**
**** * **************************************
**** * Metodos de analisis sintactico
**** * **************************************
	*/
	
	/**
	 * PROGRAMA
	 */
	public Vector<String> PROGRAMA()
	{
		// programa
		nextToken();
		emparejar("programa");
		
		// id
		nextToken();
		String id = _token.getContent();
		
		// Semantica
		_codint.addInst("prog " + id + ";");
		
		// DEC
		DEC();
		
		// DECSPROG
		DECSPROG();
		
		// comienzo
		nextToken();
		emparejar("comienzo");
		
		// LS
		LS();
		
		// fin
		nextToken();
		emparejar("fin");
		
		// ;
		nextToken();
		emparejar(";");
		
		// Semantica;
		_codint.addInst("halt;");
		return _codint.getCodigo();
	}
	
	
	/**
	 * DEC
	 */
	private void DEC()
	{	
		nextToken();
		
		// variables LID : T ; DEC
		if(_token.getContent().equals("variables"))
		{
			// variables
			emparejar("variables");
			
			//LID
			Atrib lid = LID();
			
			//:
			nextToken();
			emparejar(":");
			
			//T
			Atrib t = T();
			
			//;
			nextToken();
			emparejar(";");
			
			// Semantica
			generarListaId(lid.listaid, t.nombre);
			
			// DEC
			DEC();
		}
		// Epsilon
		else
		{
			_lectorTokens.retroceder();
		}
	}
	
	/**
	 * LID
	 */
	private Atrib LID()
	{
		// id
		nextToken();
		String id = _token.getContent();
		
		// Semantica
		Atrib ret = new Atrib();
		ret.listaid.add(id);
		
		// RLID
		Atrib rlid = RLID();
		
		// Semantica
		concatenar(ret.listaid, rlid.listaid);
		return ret;
	}
	
	
	/**
	 * RLID
	 */
	private Atrib RLID()
	{
		nextToken();
		
		// , id RLID
		if ( _token.getContent().equals(",") )
		{
			// ,
			emparejar(",");
			
			// Semantica
			return LID();
		}
		// Epsilon
		else
		{
			_lectorTokens.retroceder();
			return new Atrib();
		}
			
	}
	
	
	/**
	 * T
	 */
	private Atrib T(){
		
		nextToken();
		
		// entero
		if(_token.getContent().equals("entero")){
			
			return new Atrib("int");
			
		// real
		}else{
			
			return new Atrib("real");
			
		}
		
	}
	
	/**
	 * DECSPROG
	 */
	private void DECSPROG()
	{
		nextToken();
		
		// Procedimiento o "Epsilon"
		if(_token.getContent().equals("procedimiento"))
		{
			_lectorTokens.retroceder();
			DECSPROG2();
			DECSPROG();
		}else{
			_lectorTokens.retroceder();
		}
	}
	
	/**
	 * DECSPROG2
	 */
	private void DECSPROG2()
	{
		// CAB
		CAB();
		
		// DEC
		DEC();
		
		// comienzo
		nextToken();
		emparejar("comienzo");
		
		// LS
		LS();
		
		// fin
		nextToken();
		emparejar("fin");
		
		// ;
		nextToken();
		emparejar(";");
		
		// Semantico
		_codint.addInst("finproc;");
		_metodo = ""; // Salir metodo
	}
	
	
	/**
	 * CAB
	 */
	private void CAB()
	{
		// procedimiento
		nextToken();
		emparejar("procedimiento");
		
		// id
		nextToken();
		String id = _token.getContent();
		
		// Semantica
		_codint.addInst("proc " + id + ";");
		_metodo = id + ".";
		
		// ARGS
		ARGS(new Atrib(id));
	}
	
	
	/**
	 * ARGS
	 */
	private void ARGS(Atrib param)
	{
		nextToken();

		// (
		if(_token.getContent().equals("("))
		{
			// (
			emparejar("(");
			
			// LPARAM
			Atrib lparam = LPARAM();
			
			// )
			nextToken();
			emparejar(")");
			
			// Semantica
			generarMetodo(param.nombre, lparam.listatipos, lparam.listavalref);
		}
		else
		{	
			_lectorTokens.retroceder();	
		}
		
	}
	
	/**
	 * LPARAM
	 */
	private Atrib LPARAM()
	{
		nextToken();
		
		// LID : IO T
		if ( _token.getType() == TipoToken.IDENT )
		{
			_lectorTokens.retroceder();
			
			// LID
			Atrib lid = LID();
			
			// :
			nextToken();
			emparejar(":");
			
			// IO
			Atrib io = IO();
			
			// T
			Atrib t = T();
			
			// Semantica
			Atrib ret = new Atrib();
			ret.listatipos = crearListaTipoParams(lid.listaid.size(), t.nombre);
			ret.listavalref = crearListaValrefParams(lid.listaid.size(), io.nombre);
			generarListaId(lid.listaid, t.nombre, io.nombre);
			
			// RLPARAM
			Atrib rlparam = RLPARAM();
			
			// Semantica
			concatenar(ret.listatipos, rlparam.listatipos);
			concatenar(ret.listavalref, rlparam.listavalref);
			return ret;
		}
		// Epsilon
		else
		{
			_lectorTokens.retroceder();
			return new Atrib();
		}
	}
	
	/**
	 * RLPARAM
	 */
	private Atrib RLPARAM()
	{
		nextToken();
		
		// ; LID : IO T
		if ( _token.getContent().equals(";") )
		{
			// ;
			emparejar(";");
			
			// LPARAM -> LID : IO T = LPARAM
			return LPARAM();
		}
		// Epsilon
		else
		{
			_lectorTokens.retroceder();
			return new Atrib();
		}
	}
	
	
	/**
	 * IO
	 */
	private Atrib IO(){
		
		Atrib ret;
		nextToken();
		
		// entrada IO2
		if(_token.getContent().equals("entrada"))
		{
			// entrada
			emparejar("entrada");
			
			// IO2
			ret = IO2();
			return ret;
			
		}
		// salida
		else
		{	
			return new Atrib("ref_");
		}
		
	}
	
	
	/**
	 * IO2
	 */
	private Atrib IO2(){
		
		nextToken();
		
		// salida
		if(_token.getContent().equals("salida"))
		{
			
			emparejar("salida");
			// salida
			return new Atrib("ref_");
			
		}
		// Epsilon
		else
		{
			_lectorTokens.retroceder();
			return new Atrib("val_");	
		}
		
	}
	
	/**
	 * LS
	 */
	private Atrib LS()
	{
		// S
		Atrib s = S();
		
		// Parche si no existe S
		// Equivale al caso EPSILON
		if ( s == null )
			return new Atrib();
		
		// ;
		nextToken();
		emparejar(";");
		
		// LS
		Atrib ls = LS();
		
		// Semantica
		concatenar(s.listasalir, ls.listasalir);
		
		return s;
	}
	
	
	/**
	 * S
	 */
	private Atrib S()
	{
		nextToken();
		
		// S2
		if (_token.getType() == TipoToken.IDENT )
		{
			//
			_lectorTokens.retroceder();
			return S2();
		}
		// si EX entonces M1 LS fin si ; M2
		else if ( _token.getContent().equals("si") )
		{
			// si
			emparejar("si");
			
			// EX
			Atrib ex = EX();
			
			// entonces
			nextToken();
			emparejar("entonces");
			
			// M1
			int ref1 = _codint.getRefCodigo();
			
			// LS
			Atrib ls = LS();
			
			// fin
			nextToken();
			emparejar("fin");
			
			// si
			nextToken();
			emparejar("si");
			
			// M2
			int ref2 = _codint.getRefCodigo();
			
			// Semantica
			_codint.completarInstruccion(ex.refTrue, ref1);
			_codint.completarInstruccion(ex.refFalse, ref2);
			
			return ls;
		}
		// hacer M1 LS mientras EX fin hacer ; M2
		else if ( _token.getContent().equals("hacer") )
		{
			// hacer 
			emparejar("hacer");
			
			// M1
			int ref1 = _codint.getRefCodigo();
			
			// LS
			Atrib ls = LS();
			
			// mientras
			nextToken();
			emparejar("mientras");
			
			// EX
			Atrib ex = EX();
			
			// fin
			nextToken();
			emparejar("fin");
			
			// hacer
			nextToken();
			emparejar("hacer");
			
			// M2
			int ref2 = _codint.getRefCodigo();
			
			// Semantica
			_codint.completarInstruccion(ex.refTrue, ref1);
			_codint.completarInstruccion(ex.refFalse, ref2);
			_codint.completarInst(ls.listasalir , ref2);
			
			return new Atrib();
		}
		// salir si EX ; M
		else if ( _token.getContent().equals("salir") )
		{
			// salir
			emparejar("salir");
			
			// si
			nextToken();
			emparejar("si");
			
			// EX
			Atrib ex = EX();
			
			// M
			int ref = _codint.getRefCodigo();
			
			// Semantica
			_codint.completarInstruccion(ex.refFalse, ref);
			
			Atrib ret = new Atrib();
			ret.listasalir.add(ex.refTrue);
			return ret;
		}
		// get ( VAR ) ;
		else if ( _token.getContent().equals("get") )
		{
			// get
			emparejar("get");
			
			// (
			nextToken();
			emparejar("(");
			
			// VAR
			Atrib var = VAR();
			
			// )
			nextToken();
			emparejar(")");
			
			// Semantica
			_codint.addInst("read " + var.nombre + ";");
			return new Atrib();
		}
		// put_line ( ES ) ;
		else if ( _token.getContent().equals("put_line") )
		{
			// put_line
			emparejar("put_line");
			
			// (
			nextToken();
			emparejar("(");
			
			// ES
			Atrib es = ES();
			
			// )
			nextToken();
			emparejar(")");
			
			// Semantica
			_codint.addInst("write " + es.nombre + ";");
			_codint.addInst("writeln;");
			return new Atrib();
		}
		// NO HAY S
		else
		{
			_lectorTokens.retroceder();
			return null;
		}
	}
	
	
	
	/**
	 * S2
	 */
	private Atrib S2()
	{
		// id ó VAR
		nextToken();
		String id = _token.getContent();
		
		// ( o =
		nextToken();
		
		// VAR = ES
		if ( _token.getContent().equals("=") )
		{
			// Comprobar que id existe
			String tipo = getTipoVariable(id);
			if ( tipo == null )
				errorVarNoExistente(id);
			
			// =
			emparejar("=");
			
			// ES
			Atrib es = ES();
			
			// Semantica
			_codint.addInst(id + " := " + es.nombre + ";");
			if( !tipo.equals(es.tipo) && !es.tipo.equals("error") )
				errorTiposDistintos(tipo, es.tipo);
			
			return new Atrib();
		}
		// VAR ( FUNC )
		else
		{
			// Comprobar que el metodo existe
			Vector<Object> tipos = getTiposMetodo(id);
			if ( tipos == null )
			{
				errorMetNoExistente(id);
			}
			
			// (
			emparejar("(");
			
			// FUNC
			Atrib func = FUNC();
			
			// )
			nextToken();
			emparejar(")");
			
			// Semantica
			if ( tipos != null && comprobarParametros(tipos, func.listatipos) )
				escribirLlamada(id, func.listaid);
			
			return new Atrib();
		}
		
	}
	
	

	/**
	 * FUNC
	 */
	private Atrib FUNC()
	{
		nextToken();
		
		// ES FUNC2
		if ( _token.getType() == TipoToken.NUM_ENTERO
				|| _token.getType() == TipoToken.NUM_REAL 
				|| _token.getType() == TipoToken.IDENT
				|| _token.getContent().equals("(") )
		{
			_lectorTokens.retroceder();
			
			// ES
			Atrib es = ES();
		
			// FUNC2
			Atrib func2 = FUNC2();
			
			// Semantica
			Atrib ret = new Atrib();
			ret.listaid.add(es.nombre);
			ret.listatipos.add(es.tipo);
			concatenar(ret.listaid, func2.listaid);
			concatenar(ret.listatipos, func2.listatipos);
			return ret;
		}
		// Epsilon
		else
		{
			_lectorTokens.retroceder();
			return new Atrib();
		}
	}
	
	
	
	/**
	 * FUNC2
	 */
	private Atrib FUNC2()
	{
		nextToken();
		
		// , FUNC
		if ( _token.getContent().equals(",") )
		{
			// ,
			emparejar(",");
			
			// FUNC
			return FUNC();
		}
		// Epsilon
	 	else{
			_lectorTokens.retroceder();
			return new Atrib();
	 	}
	}
	
	
	/**
	 * VAR
	 */
	private Atrib VAR(){
		
		nextToken();
		
		// VAR.nombre = id.nombre
		String id = _token.getContent();
		
		if ( getTipoVariable(id) == null )
		{
			errorVarNoExistente(id);
			return new Atrib(id, "error");
		}
		else
			return new Atrib(id, getTipoVariable(id));	
	}
	
	
	/**
	 * EX
	 */
	private Atrib EX(){
		
		// ES
		Atrib es = ES();
		// EX2
		Atrib ex2 = EX2(es);
		
		return ex2;
	}
	
	
	/**
	 * EX2
	 */
	private Atrib EX2(Atrib param)
	{
		nextToken();
		
		// == ES
		if ( _token.getContent().equals("==") )
		{
			// ==
			emparejar("==");
			
			// ES
			Atrib es = ES();
			
			// Semantica
			Atrib ret = new Atrib(_codint.getRefCodigo(), _codint.getRefCodigo()+1);
			_codint.addInst("if " + param.nombre + " == " + es.nombre + " goto ");
			_codint.addInst("goto ");
			return ret;
		}
		// > ES
		else if ( _token.getContent().equals(">") )
		{
			// >
			emparejar(">");
			
			// ES
			Atrib es = ES();
			
			// Semantica
			Atrib ret = new Atrib(_codint.getRefCodigo(), _codint.getRefCodigo()+1);
			_codint.addInst("if " + param.nombre + " > " + es.nombre + " goto ");
			_codint.addInst("goto ");
			return ret;
		}
		// < ES
		else if ( _token.getContent().equals("<") )
		{
			// <
			emparejar("<");
			
			// ES
			Atrib es = ES();
			
			// Semantica
			Atrib ret = new Atrib(_codint.getRefCodigo(), _codint.getRefCodigo()+1);
			_codint.addInst("if " + param.nombre + " < " + es.nombre + " goto ");
			_codint.addInst("goto ");
			return ret;
		}
		// >= ES
		else if ( _token.getContent().equals(">=") )
		{
			// >=
			emparejar(">=");
			
			// ES
			Atrib es = ES();
			
			// Semantica
			Atrib ret = new Atrib(_codint.getRefCodigo(), _codint.getRefCodigo()+1);
			_codint.addInst("if " + param.nombre + " >= " + es.nombre + " goto ");
			_codint.addInst("goto ");
			return ret;
		}
		// <= ES
		else if ( _token.getContent().equals("<=") )
		{
			// <=
			emparejar("==");
			
			// ES
			Atrib es = ES();
			
			// Semantica
			Atrib ret = new Atrib(_codint.getRefCodigo(), _codint.getRefCodigo()+1);
			_codint.addInst("if " + param.nombre + " <= " + es.nombre + " goto ");
			_codint.addInst("goto ");
			return ret;
		}
		// /= ES
		else
		{
			// /=
			emparejar("/=");
			
			// ES
			Atrib es = ES();
			
			// Semantica
			Atrib ret = new Atrib(_codint.getRefCodigo(), _codint.getRefCodigo()+1);
			_codint.addInst("if " + param.nombre + " /= " + es.nombre + " goto ");
			_codint.addInst("goto ");
			return ret;
		}
	}

	
	/**
	 * ES
	 */
	private Atrib ES()
	{
		Atrib ter = TER();
		Atrib es2 = ES2(ter);
		return es2;
	}
	

	/**
	 * ES2
	 */
	private Atrib ES2(Atrib param)
	{		
		nextToken();

		// + TER ES2
		if(_token.getContent().equals("+"))
		{
			// +
			emparejar("+");
			
			// TER
			Atrib ter = TER();
			
			// Semantico
			Atrib ret;
			String aux = nuevoId();
			if( param.tipo.equals("int") && ter.tipo.equals("int") )
			{
				_codint.addInst(aux + ":=" + param.nombre + " int_add " + ter.nombre + ";");
				ret = new Atrib(aux,"int");
			}
			else if( param.tipo.equals("real") && ter.tipo.equals("real") )
			{
				_codint.addInst(aux + ":=" + param.nombre + " real_add " + ter.nombre + ";");
				ret = new Atrib(aux,"real");
			}
			else if( param.tipo.equals("int") && ter.tipo.equals("real") )
			{
				String aux2 = nuevoId();
				_codint.addInst(aux2 + ":= intareal " + param.nombre + ";");
				_codint.addInst(aux + ":=" + aux2 + " real_add " + ter.nombre + ";");
				ret = new Atrib(aux,"real");
			}
			else if (param.tipo.equals("real") && ter.tipo.equals("int"))
			{
				String aux2 = nuevoId();
				_codint.addInst(aux2 + ":= intareal " + ter.nombre + ";");
				_codint.addInst(aux + ":=" + param.nombre + " real_add " + aux2 + ";");
				ret = new Atrib(aux,"real");
			}
			else
			{
				ret = new Atrib(aux, "error");
			}
			
			// ES2
			Atrib es2 = ES2(ret);
			return es2;
		}
		// - TER ES2
		else if(_token.getContent().equals("-"))
		{
			// -
			emparejar("-");
			
			// TER
			Atrib ter = TER();
			
			// Semantico
			Atrib ret;
			String aux = nuevoId();
			if( param.tipo.equals("int") && ter.tipo.equals("int") )
			{
				_codint.addInst(aux + ":=" + param.nombre + " int_sub " + ter.nombre + ";");
				ret = new Atrib(aux,"int");
			}
			else if( param.tipo.equals("real") && ter.tipo.equals("real") )
			{
				_codint.addInst(aux + ":=" + param.nombre + " real_sub " + ter.nombre + ";");
				ret = new Atrib(aux,"real");
			}
			else if( param.tipo.equals("int") && ter.tipo.equals("real") )
			{
				String aux2 = nuevoId();
				_codint.addInst(aux2 + ":= intareal " + param.nombre + ";");
				_codint.addInst(aux + ":=" + aux2 + " real_sub " + ter.nombre + ";");
				ret = new Atrib(aux,"real");
			}
			else if ( param.tipo.equals("real") && ter.tipo.equals("int") )
			{
				String aux2 = nuevoId();
				_codint.addInst(aux2 + ":= intareal " + ter.nombre + ";");
				_codint.addInst(aux + ":=" + param.nombre + " real_sub " + aux2 + ";");
				ret = new Atrib(aux,"real");
			}
			else
			{
				ret = new Atrib(aux, "error");
			}
			
			
			// ES2
			Atrib es2 = ES2(ret);
			return es2;
		}
		// epsilon
		else
		{
			_lectorTokens.retroceder();
			return param;
		}		
	}
	/**
	 * TER
	 */
	private Atrib TER()
	{
		Atrib f = F();
		Atrib ter2 = TER2(f);
		return ter2;
	}
	
	/**
	 * TER2
	 */
	private Atrib TER2(Atrib param)
	{		
		nextToken();

		// * F TER2
		if(_token.getContent().equals("*"))
		{
			// *
			emparejar("*");
			
			// F
			Atrib f = F();
			
			// Semantico
			Atrib ret;
			String aux = nuevoId();
			if( param.tipo.equals("int") && f.tipo.equals("int") )
			{
				_codint.addInst(aux + ":=" + param.nombre + " int_mul " + f.nombre + ";");
				ret = new Atrib(aux,"int");
			}
			else if( param.tipo.equals("real") && f.tipo.equals("real") )
			{
				_codint.addInst(aux + ":=" + param.nombre + " real_mul " + f.nombre + ";");
				ret = new Atrib(aux,"real");
			} 
			else if( param.tipo.equals("int") && f.tipo.equals("real") )
			{
				String aux2 = nuevoId();
				_codint.addInst(aux2 + ":= intareal " + param.nombre + ";");
				_codint.addInst(aux + ":=" + aux2 + " real_mul " + f.nombre + ";");
				ret = new Atrib(aux,"real");
			}
			else if (param.tipo.equals("real") && f.tipo.equals("entero")) 
			{
				String aux2 = nuevoId();
				_codint.addInst(aux2 + ":= intareal " + f.nombre + ";");
				_codint.addInst(aux + ":=" + param.nombre + " real_mul " + aux2 + ";");
				ret = new Atrib(aux,"real");
			}
			// ERROR
			else
			{
				ret = new Atrib(aux, "error");
			}
			
			// TER2
			Atrib ter2 = TER2(ret);
			return ter2;
		}
		// / F TER2
		else if(_token.getContent().equals("/"))
		{
			// /
			emparejar("/");
			
			// F
			Atrib f = F();
			
			// Semantico
			Atrib ret;
			String aux = nuevoId();
			if( param.tipo.equals("int") && f.tipo.equals("int") )
			{
				_codint.addInst(aux + ":=" + param.nombre + " int_div " + f.nombre + ";");
				ret = new Atrib(aux,"int");
			}
			else if( param.tipo.equals("real") && f.tipo.equals("real") )
			{
				_codint.addInst(aux + ":=" + param.nombre + " real_div " + f.nombre + ";");
				ret = new Atrib(aux,"real");
			}
			else if( param.tipo.equals("int") && f.tipo.equals("real"))
			{
				String aux2 = nuevoId();
				_codint.addInst(aux2 + ":= intareal " + param.nombre + ";");
				_codint.addInst(aux + ":=" + aux2 + " real_div " + f.nombre + ";");
				ret = new Atrib(aux,"real");
			}
			else if (param.tipo.equals("real") && f.tipo.equals("int"))
			{
				String aux2 = nuevoId();
				_codint.addInst(aux2 + ":= intareal " + f.nombre + ";");
				_codint.addInst(aux + ":=" + param.nombre + " real_div " + aux2 + ";");
				ret = new Atrib(aux,"real");
			}
			// ERROR
			else
			{
				ret = new Atrib(aux, "error");
			}
			
			
			// TER2
			Atrib ter2 = TER2(ret);
			return ter2;
		}
		// epsilon
		else
		{
			_lectorTokens.retroceder();
			return param;
		}		
	}
	
	/**
	 * F
	 */
	
	private Atrib F()
	{
		nextToken();
		
		// num_entero
		if ( _token.getType() == TipoToken.NUM_ENTERO )
		{
			return new Atrib(_token.getContent(), "int");
		}
		// num_real
		else if ( _token.getType() == TipoToken.NUM_REAL )
		{
			return new Atrib(_token.getContent(), "real");
		}
		// ident
		else if ( _token.getType() == TipoToken.IDENT)
		{
			String id = _token.getContent();
			
			if ( getTipoVariable(id) == null )
			{
				errorVarNoExistente(id);
				return new Atrib(id, "error");
			}
			else
				return new Atrib(id, getTipoVariable(id));
		}
		// ( ES )
		else
		{	
			// (
			emparejar("(");
			
			// ES
			Atrib es = ES();
			
			// )
			nextToken();
			emparejar(")");
			
			//Semantica
			return es;
		}
	}
}
